В Москве открывается небольшое кафе. Оно оригинальное — гостей должны обслуживать роботы. Инвесторов интересует текущее положение дел на рынке, подтверждающее потенциально высокую популярность идеи и востребованность проекта в долгосрочном периоде.
Цель проекта - провести исследование рынка на основе открытых данных о заведениях общественного питания в Москве, дать рекомендации о виде заведения, количестве посадочных мест, а также районе расположения, прокомментировать возможность развития сети.
Для этого необходимо:
# импортируем необходимые библиотеки
import pandas as pd
import numpy as np
import plotly
import plotly.express as px
from plotly import graph_objects as go
import plotly.io as pio
pio.templates.default = 'seaborn'
import requests
from io import BytesIO
pd.options.display.float_format = '{:,.2f}'.format
# %%HTML
# <style type="text/css">
# table.dataframe td, table.dataframe th {
# border: 1px black solid !important;
# color: black !important;
# }
# прочитаем DataFrame
try:
df = pd.read_csv('rest_data.csv') # локальный путь
except:
df = pd.read_csv('/datasets/rest_data.csv') # путь на сервере
# выведем на экран таблицу 10 случайных строк таблицы
df.sample(10)
# посмотрим сводную информацию таблицы
df.info()
В таблице 15366 строк, 6 столбцов, тип данных у четырех столбцов строковый, у двух - целочисленный.
id¶# посмотрим количество уникальных значений в столбце
df['id'].value_counts()
В таблице представлена информация по 15366 уникальным объектам общественного питания.
object_name¶# посмотрим количество уникальных значений в столбце
df['object_name'].value_counts()
Уникальных названий объектов - 10393. Больше всего объектов с обезличенными названиями - "Столовая" - 267, "Кафе" - 236, "Шаурма" - 234. Кроме того одинаковые названия у объектов из одной сети.
Проверим имеются ли одинаковые названия у сетевых и несетевых объектов.
# создадим таблицу с объектами, имеющими одинаковые названия и относящимися к разным типам
chain_joint = df.pivot_table(index = 'object_name',
columns = 'chain',
values = 'id',
aggfunc = 'count')\
.reset_index()
chain_joint = chain_joint[(chain_joint['да'].notnull()) & (chain_joint['нет'].notnull())]
chain_joint
# посчитаем количество объектов, имеющих одинаковые названия и относящихся к разным типам
len(chain_joint)
Имеется 29 названий объектов, которые относятся к сетевым, но в то же время использующиеся и несетевыми заведениями. Возможно, это ошибка в имеющейся информации, однако может быть и нелегальное использование названий сетевых объектов. Учитывая имеющуюся неопределенность, оставим данное положение без изменений.
chain¶# посмотрим количество уникальных значений в столбце
df['chain'].value_counts()
Сетевых объектов 2968, несетевых - 12398. Для упрощения можно заменить значения "да" на "chain", а "нет" - на "not_chain".
object_type¶# посмотрим количество уникальных значений в столбце
df['object_type'].value_counts()
Имеется информация по 9 типам объектов общественного питания. По типам объектов наибольшее количество кафе - 6099, наименьшее - отделов кулинарии - 273. Для упрощения можно заменить длинные названия типов объектов на короткие: соответственно "предприятие быстрого обслуживания" на "фаст-фуд", а "магазин (отдел кулинарии)" на "кулинария".
address¶# посмотрим количество уникальных значений в столбце
df['address'].value_counts()
Заведения расположены по 9108 адресам, т.е. имеется множество объектов, расположенных по одному адресу. Вероятнее всего, это крупные торговые центры. Наибольшее количество объектов общественного питания расположено по адресу: город Москва, Ходынский бульвар, дом 4, - 95. Для дальнейшего исследования необходимо у значений заменить букву "ё" на букву "е".
Проверим, имеются ли объекты с одинаковым названием, располагающиеся по одному и тому же адресу.
# создадим таблицу с объектами, имеющими одинаковые названия и располагающимися по одному и тому же адресу
address_joint = df.pivot_table(index = ['address', 'object_name'],
values = 'id',
aggfunc = 'count')\
.reset_index()\
.sort_values(by = 'id',
ascending = False)
address_joint = address_joint.query('id > 1')
address_joint
Имеется 137 заведений, имеющих одинаковые названия и располагающихся по одному и тому же адресу. В основном это объекты с обезличенными названиями, кроме того, вероятно, могут открываться одинаковые объекты при избыточном спросе, а также в учебных заведениях с большим количеством корпусов.
number¶# посмотрим количество уникальных значений в столбце
df['number'].value_counts()
У 1621 объекта общественного питания отсутствуют посадочные места. Вероятнее всего, это небольшие заведения по размеру, предлагающие блюда на вынос. Для удобства можно поменять название столбца.
# проведем базовую проверку столбца
df['number'].describe()
Среднее количество посадочных мест - около 60, максимальное - 1700. Половина всех объектов общественного питания имеет до 40 посадочных мест.
# посмотрим на самые большие объекты общественного питания
df.query('number >= 1000')
Имеется 7 заведений, у которых количество посадочных мест более 1000.
# определим количество пропущенных значений в таблице
df.isnull().sum()
Пропущенные значения отсутствуют.
# посчитаем количество дубликатов
df.duplicated().sum()
Дубликаты отсутствуют.
При изучении таблицы с данными установлено, что в ней 15366 строк, 6 столбцов, тип данных у четырех столбцов строковый, у двух - целочисленный, пропущенные значения и дубликаты отсутствуют. Кроме того:
id:object_name:chain:object_type:address:number:Проанализировав вышеизложенное, необходимо выполнить следующее:
chain на "chain" и "not_chain"; object_type на короткие; address заменить букву "ё" на букву "е"; number.# заменим значения в столбце отношения объекта к сетевым
df['chain'] = df['chain'].map({'нет': 'not_chain','да': 'chain'})
df.info()
# заменим длинные названия типов объектов на короткие
df['object_type'] = df['object_type'].replace('предприятие быстрого обслуживания', 'фаст-фуд')
df['object_type'] = df['object_type'].replace('магазин (отдел кулинарии)', 'кулинария')
df['object_type'].unique()
# заменим букву "ё" на букву "е" в столбце с адресом объекта
df['address'] = df['address'].str.replace('ё', 'е')
# поменяем название столбца 'number'
df = df.rename(columns = {'number':'number_of_seats'})
df.columns
В целях подготовки данных провели следующую работу:
chain на "chain" и "not_chain"; object_type на короткие; address заменили букву "ё" на букву "е"; number.# содадим таблицу с количеством объектов общественного питания по видам
object_count_type = df.groupby('object_type')\
.agg({'id': 'count'})\
.reset_index()\
.rename(columns = {'id' : 'count'})\
.sort_values(by = 'count',
ascending = False)\
.reset_index(drop = True)
object_count_type
# построим график количества объектов общественного питания по видам
fig = px.bar(object_count_type,
x = 'object_type',
y = 'count',
color = 'object_type')
fig.update_layout(title = 'Количество объектов общественного питания по видам',
xaxis_title = 'Вид объекта',
yaxis_title = 'Количество объектов',
showlegend = False,
margin = dict(l = 0, r = 0, t = 70, b = 20))
fig.update_traces(hovertemplate = 'Вид: %{x}<br>Количество: %{y}')
fig.show()
# построим график соотношения объектов общественного питания по видам
fig = go.Figure()
fig.add_trace(go.Pie(labels = object_count_type['object_type'],
values = object_count_type['count'],
hole = 0.55))
fig.update_layout(annotations = [dict(text = 'Соотношение объектов<br>общественного питания<br>по видам',
font_size = 20,
showarrow = False)],
legend = dict(x = 0.78,
font_size = 16),
margin = dict(l = 0, r = 0, t = 20, b = 20))
fig.update_traces(textposition = 'inside',
textfont_size = 16)
fig.show()
В Москве самым популярным видом объекта общественного питания является кафе - имеется 6099 таких заведений (39,7% от общего количества объектов). Достаточно большую долю на рынке занимают столовые, рестораны и предприятия быстрого обслуживания (фаст-фуды), которая составляет соответственно 16,8% (2587 заведений), 14,9% (2285 заведений) и 12,5% (1923 заведения). Менее 3% доля от общего количества объектов у отделов кулинарий магазинов, закусочных и кафетериев.
# содадим таблицу с количеством объектов общественного питания по отношению к сетевым
object_count_chain = df.groupby('chain')\
.agg({'id': 'count'})\
.reset_index()\
.rename(columns = {'id' : 'count'})\
.sort_values(by = 'count',
ascending = False)\
.reset_index(drop = True)
object_count_chain['chain'] = object_count_chain['chain'].map({'not_chain': 'несетевой','chain': 'сетевой'})
object_count_chain
# построим график количества объектов общественного питания по отношению к сетевым
fig = px.bar(object_count_chain,
x = 'chain',
y = 'count',
color = 'chain')
fig.update_layout(title = 'Количество объектов общественного питания по отношению к сетевым',
xaxis_title = 'Тип объекта',
yaxis_title = 'Количество объектов',
showlegend = False,
margin = dict(l = 0, r = 0, t = 70, b = 0))
fig.update_traces(hovertemplate = 'Тип: %{x}<br>Количество: %{y}')
fig.show()
# построим график соотношения объектов общественного питания по отношению к сетевым
fig = go.Figure()
fig.add_trace(go.Pie(labels = object_count_chain['chain'],
values = object_count_chain['count'],
hole = 0.55))
fig.update_layout(annotations = [dict(text = 'Соотношение объектов<br>общественного питания<br>по отношению к сетевым',
font_size = 18,
showarrow = False)],
showlegend = False,
margin = dict(l = 0, r = 0, t = 20, b = 20))
fig.update_traces(textposition = 'inside',
textinfo = 'percent + label',
textfont_size = 16)
fig.show()
В Москве преобладают несетевые объекты общественного питания, их доля от общего количества заведений составляет 80,7% (количество - 12398). Объектов, принадлежащих различным сетям, 2968 (19,3%).
# создадим таблицу с количеством объектов общественного питания по отношению к сетевым по видам
object_count_type_chain = df.pivot_table(index = 'object_type',
columns = 'chain',
values = 'id',
aggfunc = 'count')\
.sort_values(by = 'chain',
ascending = False)\
.reset_index()
object_count_type_chain
# построим график количества объектов общественного питания по отношению к сетевым по видам
fig = go.Figure()
fig.add_trace(go.Bar(x = object_count_type_chain['object_type'],
y = object_count_type_chain['chain'],
name = 'сетевой'))
fig.add_trace(go.Bar(x = object_count_type_chain['object_type'],
y = object_count_type_chain['not_chain'],
name = 'несетевой'))
fig.update_layout(title = 'Количество объектов общественного питания по отношению к сетевым по видам',
xaxis_title = 'Вид объекта',
yaxis_title = 'Количество объектов',
margin = dict(l = 0, r = 0, t = 70, b = 0))
fig.update_traces(hoverinfo = 'all', hovertemplate = 'Вид: %{x}<br>Количество: %{y}')
fig.show()
В абсолютном выражении среди сетевых объектов общественного питания больше всего кафе, вероятнее всего из-за большей распространенности заведений этого вида.
# построим график соотношения объектов общественного питания по отношению к сетевым по видам
fig = go.Figure()
fig.add_trace(go.Scatter(x = object_count_type_chain['object_type'],
y = object_count_type_chain['chain'],
mode = 'lines + markers',
name = 'сетевой',
stackgroup = 'one',
groupnorm = 'percent'))
fig.add_trace(go.Scatter(x = object_count_type_chain['object_type'],
y = object_count_type_chain['not_chain'],
mode = 'lines',
name = 'несетевой',
stackgroup = 'one',
groupnorm = 'percent'))
fig.update_layout(yaxis = dict(range = [0.1, 100],
ticksuffix = '%'),
title = 'Соотношение объектов общественного питания по отношению к сетевым по видам',
xaxis_title = 'Вид объекта',
yaxis_title = 'Доля объектов',
legend_orientation = 'h',
margin = dict(l = 0, r = 20, t = 70, b = 0))
fig.update_traces(hovertemplate = 'Вид: %{x}<br>Доля: %{y}')
fig.show()
При изучении относительных показателей картина немного иная. Наибольшая доля сетевых заведений у фаст-фудов (41,1%) и кулинарий (28,6%). У самого распространенного вида объектов общественного питания - кафе - доля сетевых заведений составляет 22,3%. Наименьшее и абсолютное количество сетевых заведений, и доля от всех объектов данного вида у столовых и буфетов.
# посмотрим статистические показатели количества посадочных мест сетевых и несетевых объектов общественного питания
stat_chain = df.query('chain == "chain"')\
['number_of_seats'].describe()\
.reset_index()\
.rename(columns = {'index' : 'parameter',
'number_of_seats' : 'chain'})
stat_not_chain = df.query('chain == "not_chain"')\
['number_of_seats'].describe()\
.reset_index()\
.rename(columns = {'index' : 'parameter',
'number_of_seats' : 'not_chain'})
stat_all = stat_chain.merge(stat_not_chain,
on = 'parameter')
stat_all
Среднее количество посадочных мест в сетевых объектах общественного питания 53, медианное - 40. Для 75% сетевых заведений число посадочных мест не превысило 72. Для несетевых заведений характерен больший разброс значений количества посадочных мест, при этом при одинаковых медианных значениях количества мест у сетевых и несетевых объектов, среднее арифиметическое у несетевых заведений значительно выше.
# построим график распределения сетевых объектов общественного питания по количеству посадочных мест
fig = px.histogram(df.query('chain == "chain"'),
x = 'number_of_seats',
marginal = 'box')
fig.update_layout(title = 'Распределение сетевых объектов общественного питания по количеству посадочных мест',
xaxis_title = 'Количество посадочных мест',
yaxis_title = 'Количество объектов',
margin = dict(l = 0, r = 0, t = 70, b = 0))
fig.update_traces(hovertemplate = 'Мест: %{x}<br>Количество: %{y}')
fig.show()
Исходя из построенного графика, можно утверждать, что для сетевых объектов общественного питания Москвы характерно много заведений с небольшим количеством посадочных мест в каждом, и мало заведений с большим числом посадочных мест в каждом.
# построим график распределения количества посадочных мест по видам объектов общественного питания
fig = px.box(df,
x = 'object_type',
y = 'number_of_seats',
color = 'object_type')
fig.update_layout(title = 'Распределение количества посадочных мест по видам объектов общественного питания',
xaxis_title = 'Вид объекта',
yaxis_title = 'Количество посадочных мест',
yaxis = dict(range = [-10, 400]),
showlegend = False,
margin = dict(l = 0, r = 0, t = 70, b = 0))
fig.update_traces(hovertemplate = 'Вид: %{x}<br>Количество: %{y}')
fig.show()
Для каждого из видов объектов общественного питания характерна следующая закономерность: имеется небольшое количество заведений с большим числом посадочных мест и большое количество объектов с их небольшим числом. Наибольшее количество посадочных мест из видов объектов общественного питания у столовых, у них же наибольший разброс этих значений. У закусочных и кулинарий более половины заведений не имеет посадочных мест, а у кафетериев и фаст-фудов посадочных мест не имеют более 25% объектов.
Учитывая большой разброс значений числа посадочных мест по всем видам объектов общественного питания, средним их количеством будем считать медиану.
# создадим таблицу со средним количеством посадочных мест по видам объектов общественного питания
object_type_seats = df.groupby('object_type')\
.agg({'number_of_seats' : 'median'})\
.sort_values(by = 'number_of_seats',
ascending = False)\
.rename(columns = {'number_of_seats' : 'number_of_seats_mean'})\
.reset_index()
object_type_seats
# построим график среднего количества посадочных мест по видам объектов общественного питания
fig = px.bar(object_type_seats,
x = 'object_type',
y = 'number_of_seats_mean',
color = 'object_type')
fig.update_layout(title = 'Среднее количество посадочных мест по видам объектов общественного питания',
xaxis_title = 'Вид объекта',
yaxis_title = 'Количество посадочных мест',
showlegend = False,
margin = dict(l = 0, r = 0, t = 70, b = 0))
fig.update_traces(hovertemplate = 'Вид: %{x}<br>Количество: %{y}')
fig.show()
Наибольшее среднее количество посадочных мест у столовых - 103, и ресторанов - 80. У самого распространенного вида заведений - кафе - среднее количество посадочных мест достаточно небольшое - 30. У объектов с самой большой долей сетевых заведений - фаст-фудов - среднее количество посадочных мест совсем мало - 5. У закусочных и кулинарий среднее количество посадочных мест равно 0.
Так как улица размещения объекта общественного питания является одним из важнейших факторов для принятия решения об открытии заведения, необходимо изучить столбец с адресами объектов подробнее, исключив из таблицы строки с заведениями из Зеленограда и населенных пунктов Новой Москвы, в связи с тем, что в них могут быть такие же названия улиц, как и в самой Москве.
# удалим из таблицы строки с заведениями из Зеленограда и населенных пунктов Новой Москвы
for city in ['город Зеленоград',
'поселение',
'город Троицк',
'город Щербинка',
'город Московский',
'поселок Внуково',
'поселок Акулово',
'деревня Толстопальцево']:
df = df[~df['address'].apply(lambda x: city in x)]
df.info()
Удалили 829 строк, или 5,4%.
# проверим, во всех ли строках столбца с адресом объекта указан город
df[~df['address'].apply(lambda x: 'город Москва' in x)]
Имеется 63 строки, в которых в столбце с адресом объекта общественного питания не указан город.
# добавим город в столбец с адресом объекта общественного питания
df.loc[~df['address'].apply(lambda x: 'город Москва' in x), 'address'] =\
'город Москва, ' + df.loc[~df['address'].apply(lambda x: 'город Москва' in x), 'address']
df.loc[~df['address'].apply(lambda x: 'город Москва' in x), 'address']
# создадим таблицу с разделением адреса объекта общественного питания на столбцы
address_table = df['address'].str.split(', ', expand = True)
address_table.head()
Навание улицы оказалось в столбце 1.
# добавим в таблицу столбец с улицей объекта общественного питания
df['street'] = address_table[1]
df.head()
Таким образом, в исходную таблицу добавлен столбец с улицей, на которой размещен объект общественного питания.
Получим из внешнего источника информацию о принадлежности улиц к районам Москвы. Для уменьшения размера таблицы из нее предварительно были удалены ненужные столбцы.
# выгрузим файл
spreadsheet_id = '1LnuSRFScSIh8fVxsDPgJa4NoXutu7NzNM1lT7RHPAuQ'
file_name = 'https://docs.google.com/spreadsheets/d/{}/export?format=csv'.format(spreadsheet_id)
r = requests.get(file_name)
open_data = pd.read_csv(BytesIO(r.content))
open_data.head()
# приведем названия столбцов к нижнему регистру
open_data.columns = open_data.columns.str.lower()
open_data.head()
# заменим букву "ё" на букву "е" в столбце с адресом объекта
open_data['address'] = open_data['address'].str.replace('ё', 'е')
# добавим в исходную таблицу сведения об административном и муниципальном округах,
# в которых находятся объекты общественного питания
df_new = df.merge(open_data,
on = 'address',
how = 'left')
df_new.info()
# посчитаем количество дубликатов
df_new.duplicated().sum()
# удалим дубликаты
df_new = df_new.drop_duplicates().reset_index(drop = True)
df_new.info()
# определим долю пропущенных значений в таблице
df_new.isnull().mean() * 100
Не заполнилось 1,2% значений в столбцах административного и муниципального округов объекта общественного питания. Попробуем их заполнить по названию улицы.
# создадим список с улицами, у которых есть незаполненные значения административного и муниципального округов
area_isna = (df_new['adm_area'].isna() == True) & (df_new['district'].isna() == True)
unnamed_streets = df_new.loc[area_isna, 'street'].to_list()
unnamed_streets
# создадим таблицу с разделением адреса в таблице, полученной из внешнего источника, на столбцы
address_open_data = open_data['address'].str.split(', ', expand = True)
address_open_data.head()
# добавим в таблицу столбец с улицей объекта
open_data['street'] = address_open_data[1]
open_data.head()
# заполним пропущенные значения административного и муниципального округов объекта общестенного питания
for street in unnamed_streets:
try:
adm_area_value = open_data.query('street == @street')['adm_area'].unique()[0]
df_new.loc[(df_new['street'] == street) & area_isna, 'adm_area'] = adm_area_value
district_value = open_data.query('street == @street')['district'].unique()[0]
df_new.loc[(df_new['street'] == street) & area_isna, 'district'] = district_value
except:
df_new.loc[(df_new['street'] == street) & area_isna, 'adm_area'] = np.nan
df_new.loc[(df_new['street'] == street) & area_isna, 'district'] = np.nan
df_new.info()
В столбцах административного и муниципального округов объекта общественного питания остались незаполненными 2 строки, удалим их.
# удалим строки с пропущенными значениями административного и муниципального округов
df_new.dropna(subset = ['adm_area'],
inplace = True)
df_new = df_new.reset_index(drop = True)
df_new.isnull().sum()
# для удобства визуализации сократим названия административного и муниципального округов
df_new['adm_area'] = df_new['adm_area'].str.replace(' административный округ', '')
df_new['district'] = df_new['district'].str.replace('муниципальный округ ', '')
df_new.head()
Таблица готова для последующего анализа.
# выделим топ-10 улиц с наибольшим количеством объектов общественного питания
top_10_streets = df_new.groupby('street')\
['id'].count()\
.reset_index()\
.sort_values(by = 'id',
ascending = False)\
.rename(columns = {'id' : 'count'})\
.reset_index(drop = True)\
.head(10)
top_10_streets
# построим график топ-10 улиц с наибольшим количеством объектов общественного питания
fig = px.bar(top_10_streets,
x = 'count',
y = 'street',
color = 'street')
fig.update_layout(title = 'Топ-10 улиц с наибольшим количеством объектов общественного питания',
xaxis_title = 'Количество объектов',
yaxis_title = 'Улица',
showlegend = False,
margin = dict(l = 0, r = 0, t = 70, b = 0))
fig.update_traces(hovertemplate = 'Количество: %{x}<br>Улица: %{y}')
fig.show()
Наибольшее количество объектов общественного питания Москвы находится на проспекте Мира - 204, на Профсоюзной улице размещено 183 заведения. Кроме перечисленных, более 150 объектов находятся на Ленинградском проспекте, Пресненской набережной и Варшавском шоссе.
# создадим список с топ-10 улиц с наибольшим количеством объектов общественного питания
top_10_streets_list = top_10_streets['street'].to_list()
top_10_streets_list
# создадим таблицу топ-10 улиц с наибольшим количеством объектов общественного питания с учетом административного округа
top_10_streets_area = df_new.query('street == @top_10_streets_list')\
.groupby(['street', 'adm_area'])\
['id'].count()\
.reset_index()\
.sort_values(by = ['street', 'id'],
ascending = False)\
.rename(columns = {'id' : 'count'})\
.reset_index(drop = True)
top_10_streets_area
# посчитаем количество административных округов,
# к которым относится топ-10 улиц с наибольшим количеством объектов общественного питания
top_10_streets_area.groupby('street')\
['adm_area'].nunique()\
.sort_values(ascending = False)
# построим график топ-10 улиц с наибольшим количеством объектов общественного питания с учетом административного округа
fig = px.bar(top_10_streets_area,
x = 'count',
y = 'street',
color = 'adm_area',
labels = {'adm_area' : 'Округ'})
fig.update_layout(title = 'Топ-10 улиц с наибольшим количеством объектов общественного питания',
xaxis_title = 'Количество объектов',
yaxis_title = 'Улица',
margin = dict(l = 0, r = 0, t = 70, b = 0))
fig.update_traces(hovertemplate = 'Количество: %{x}<br>Улица: %{y}')
fig.show()
Объекты общественного питания, находящиеся на Ленинском проспекте, размещены в 4 разных административных округах Москвы, в 2 разных административных округах размещены заведения, находящиеся на проспекте Мира, проспекте Вернадского и Варшавском шоссе.
# создадим таблицу топ-10 улиц с наибольшим количеством объектов общественного питания с учетом муниципального округа
top_10_streets_district = df_new.query('street == @top_10_streets_list')\
.groupby(['street', 'district'])\
['id'].count()\
.reset_index()\
.sort_values(by = ['street', 'id'],
ascending = False)\
.rename(columns = {'id' : 'count'})\
.reset_index(drop = True)
top_10_streets_district
# посчитаем количество муниципальных округов,
# к которым относится топ-10 улиц с наибольшим количеством объектов общественного питания
top_10_streets_district.groupby('street')\
['district'].nunique()\
.sort_values(ascending = False)
# построим график топ-10 улиц с наибольшим количеством объектов общественного питания с учетом муниципального округа
fig = px.bar(top_10_streets_district,
x = 'count',
y = 'street',
color = 'district',
labels = {'district' : 'Муниципальный округ'})
fig.update_layout(title = 'Топ-10 улиц с наибольшим количеством объектов общественного питания',
xaxis_title = 'Количество объектов',
yaxis_title = 'Улица',
margin = dict(l = 0, r = 0, t = 70, b = 0))
fig.update_traces(hovertemplate = 'Количество: %{x}<br>Улица: %{y}')
fig.show()
Объекты общественного питания, находящиеся на Ленинском проспекте, размещены в 8 разных муниципальных округах Москвы, в 7 разных муниципальных округах размещены заведения, находящиеся на Варшавском шоссе, в 6 - на проспекте Мира и Профсоюзной улице.
Учитывая достаточно большую протяженность улиц, что приводит к тому, что они могут проходить через разные административные и муниципальные округа, анализ размещения объектов общественного питания целесообразнее проводить по муниципальным округам ввиду их более компактного расположения, либо по улицам с учетом принадлежности их частей конкретным муниципальным округам.
# выделим топ-10 муниципальных округов с наибольшим количеством объектов общественного питания
top_10_district = df_new.groupby('district')\
['id'].count()\
.reset_index()\
.sort_values(by = 'id',
ascending = False)\
.rename(columns = {'id' : 'count'})\
.reset_index(drop = True)\
.head(10)
top_10_district
# построим график топ-10 муниципальных округов с наибольшим количеством объектов общественного питания
fig = px.bar(top_10_district,
x = 'count',
y = 'district',
color = 'district')
fig.update_layout(title = 'Топ-10 муниципальных округов с наибольшим количеством объектов общественного питания',
xaxis_title = 'Количество объектов',
yaxis_title = 'Муниципальный округ',
showlegend = False,
margin = dict(l = 0, r = 0, t = 70, b = 0))
fig.update_traces(hovertemplate = 'Количество: %{x}<br>Округ: %{y}')
fig.show()
Наибольшее количество объектов общественного питания Москвы находится в Тверском (778) и Пресненском (722) муниципальных округах. Кроме перечисленных, более 400 заведений в Басманном и Даниловском муниципальных округах.
# выделим топ-10 улиц с наибольшим количеством объектов общественного питания
# с учетом принадлежности их частей конкретным муниципальным округам
top_10_part_streets_district = df_new.groupby(['street', 'district'])\
['id'].count()\
.reset_index()\
.sort_values(by = 'id',
ascending = False)\
.rename(columns = {'id' : 'count'})\
.reset_index(drop = True)\
.head(10)
top_10_part_streets_district
# построим график топ-10 улиц с наибольшим количеством объектов общественного питания
# с учетом принадлежности их частей конкретным муниципальным округам
fig = px.bar(top_10_part_streets_district,
x = 'count',
y = 'street',
color = 'district',
labels = {'district' : 'Муниципальный округ'})
fig.update_layout(title = 'Топ-10 улиц с наибольшим количеством объектов общественного питания\
<br>с учетом принадлежности их частей конкретным муниципальным округам',
xaxis_title = 'Количество объектов',
yaxis_title = 'Улица',
margin = dict(l = 0, r = 0, t = 70, b = 0))
fig.update_traces(hovertemplate = 'Количество: %{x}<br>Улица: %{y}')
fig.show()
Наибольшее количество объектов общественного питания Москвы с учетом принадлежности частей улиц конкретным муниципальным округам находится на Пресненской набережной, которая целиком расположена в Пресненском муниципальном округе (167), и Ходынском бульваре, также целиком расположенном в Хорошевском муниципальном округе (102, однако как уже отмечалось ранее, 95 из них находятся по одному адресу). На двух улицах муниципального округа Арбат - Новый Арбат и Арбат - расположено в сумме 169 заведений общественного питания.
# определим количество улиц с одним объектом общественного питания
streets_one_object = df_new.groupby('street')\
.filter(lambda x: len(x) == 1)\
.groupby(['street', 'adm_area', 'district'])\
['id'].count()\
.reset_index()\
.rename(columns = {'id' : 'count'})\
.reset_index(drop = True)
len(streets_one_object)
Из всех улиц Москвы с объектами общественного питания на 537 из них находится всего по одному заведению.
# определим количество улиц с одним объектом общественного питания по административным округам
one_object_area = streets_one_object.groupby('adm_area')\
['count'].count()\
.reset_index()\
.sort_values(by = 'count',
ascending = False)\
.reset_index(drop = True)
one_object_area
# построим график количества улиц с одним объектом общественного питания по административным округам
fig = px.bar(one_object_area,
x = 'count',
y = 'adm_area',
color = 'adm_area')
fig.update_layout(title = 'Количество улиц с одним объектом общественного питания по административным округам',
xaxis_title = 'Количество улиц',
yaxis_title = 'Округ',
showlegend = False,
margin = dict(l = 0, r = 0, t = 70, b = 0))
fig.update_traces(hovertemplate = 'Количество: %{x}<br>Округ: %{y}')
fig.show()
Наибольшее количество улиц с одним объектом общественного питания в Центральном административном округе - 169. Вероятнее всего, это связано с небольшой продолжительностью улиц в этом округе и большим их количеством.
# выделим топ-10 муниципальных округов с наибольшим количеством улиц с одним объектом общественного питания
top_10_one_object_district = streets_one_object.groupby('district')\
['count'].count()\
.reset_index()\
.sort_values(by = 'count',
ascending = False)\
.reset_index(drop = True)\
.head(10)
top_10_one_object_district
# построим график топ-10 муниципальных округов с наибольшим количеством улиц с одним объектом общественного питания
fig = px.bar(top_10_one_object_district,
x = 'count',
y = 'district',
color = 'district')
fig.update_layout(title = 'Топ-10 муниципальных округов с наибольшим количеством улиц\
<br>с одним объектом общественного питания',
xaxis_title = 'Количество улиц',
yaxis_title = 'Муниципальный округ',
showlegend = False,
margin = dict(l = 0, r = 0, t = 70, b = 0))
fig.update_traces(hovertemplate = 'Количество: %{x}<br>Округ: %{y}')
fig.show()
Наибольшее количество улиц с одним объектом общественного питания в Таганском (27), Басманном (22) и Тверском (20) муниципальных округах, а также муниципальном округе Хамовники (26). 6 из 10 муниципальных округов из топ-10 округов с наибольшим количеством заведений попали в топ-10 округов с наибольшим количеством улиц с одним объектом общественного питания. Вероятнее всего, это связано с небольшой продолжительностью улиц в этих округах и большим их количеством.
# создадим таблицу со средним количеством посадочных мест в объектах общественного питания на улицах с наибольшим их числом
top_10_streets_seats_mean = df_new.query('street == @top_10_streets_list')\
.groupby('street')\
['number_of_seats'].median()\
.reset_index()\
.rename(columns = {'number_of_seats' : 'number_of_seats_mean'})\
.sort_values(by = 'number_of_seats_mean',
ascending = False)\
.reset_index(drop = True)
top_10_streets_seats_mean
# построим график распределения количества посадочных мест в объектах общественного питания
# на улицах с наибольшим их числом
fig = px.box(df_new.query('street == @top_10_streets_list'),
x = 'number_of_seats',
y = 'street',
color = 'street')
fig.update_layout(title = 'Распределение количества посадочных мест в объектах общественного питания\
<br>на улицах с наибольшим их числом',
xaxis_title = 'Количество посадочных мест',
yaxis_title = 'Улица',
xaxis = dict(range = [-10, 400]),
showlegend = False,
margin = dict(l = 0, r = 0, t = 70, b = 0))
fig.update_traces(hovertemplate = 'Количество: %{x}<br>Улица: %{y}')
fig.show()
Наибольшее среднее количество посадочных мест (медиана) среди топ-10 улиц с наибольшим числом объектов общественного питания у проспекта Мира (46,5) и Ленинского проспекта (45), наименьшее - у Профсоюзной улицы (24) и Каширского шоссе (25). Наибольший разброс количества посадочных мест характерен для Кутузовского проспекта, Каширского шоссе и проспекта Мира.
Как отмечалось ранее, учитывая достаточно большую протяженность улиц, что приводит к тому, что они могут проходить через разные административные и муниципальные округа, анализ целесообразнее проводить по муниципальным округам ввиду их более компактного расположения, либо по улицам с учетом принадлежности их частей конкретным муниципальным округам.
# создадим список с топ-10 районов с наибольшим количеством объектов общественного питания
top_10_district_list = top_10_district['district'].to_list()
top_10_district_list
# создадим таблицу со средним количеством посадочных мест
# в объектах общественного питания в муниципальных округах с наибольшим их числом
top_10_district_seats_mean = df_new.query('district == @top_10_district_list')\
.groupby('district')\
['number_of_seats'].median()\
.reset_index()\
.rename(columns = {'number_of_seats' : 'number_of_seats_mean'})\
.sort_values(by = 'number_of_seats_mean',
ascending = False)\
.reset_index(drop = True)
top_10_district_seats_mean
# построим график распределения количества посадочных мест в объектах общественного питания
# в муниципальных округах с наибольшим их числом
fig = px.box(df_new.query('district == @top_10_district_list'),
x = 'number_of_seats',
y = 'district',
color = 'district')
fig.update_layout(title = 'Распределение количества посадочных мест в объектах общественного питания\
<br>в муниципальных округах с наибольшим их количеством',
xaxis_title = 'Количество посадочных мест',
yaxis_title = 'Муниципальный округ',
xaxis = dict(range = [-10, 400]),
showlegend = False,
margin = dict(l = 0, r = 0, t = 70, b = 0))
fig.update_traces(hovertemplate = 'Количество: %{x}<br>Округ: %{y}')
fig.show()
Наибольшее среднее количество посадочных мест (медиана) среди топ-10 муниципальных округов с наибольшим числом объектов общественного питания у Тверского (50) и Таганского (45) округов, наименьшее - у Хорошевского и Даниловского (по 20) округов. Наибольший разброс количества посадочных мест характерен для Тверского, Таганского и Мещанского муниципальных округов.
# создадим список с топ-10 улиц с наибольшим количеством объектов общественного питания
# с учетом принадлежности их частей конкретным муниципальным округам
top_10_part_streets_list = top_10_part_streets_district['street'].to_list()
top_10_part_district_list = top_10_part_streets_district['district'].to_list()
top_10_part_streets_list
# создадим таблицу со средним количеством посадочных мест в объектах общественного питания
# на улицах с наибольшим их числом с учетом принадлежности их частей конкретным муниципальным округам
top_10_part_streets_district_seats_mean = df_new.query('street == @top_10_part_streets_list\
& district == @top_10_part_district_list')\
.groupby(['street',
'district'])\
['number_of_seats'].median()\
.reset_index()\
.rename(columns = {'number_of_seats' : 'number_of_seats_mean'})\
.sort_values(by = 'number_of_seats_mean',
ascending = False)\
.reset_index(drop = True)
top_10_part_streets_district_seats_mean
# построим график распределения количества посадочных мест в объектах общественного питания
# на улицах с наибольшим их числом с учетом принадлежности их частей конкретным муниципальным округам
fig = px.box(df_new.query('street == @top_10_part_streets_list & district == @top_10_part_district_list'),
x = 'number_of_seats',
y = 'street',
color = 'street')
fig.update_layout(title = 'Распределение количества посадочных мест в объектах общественного питания\
<br>на улицах с наибольшим их числом с учетом принадлежности\
<br>их частей конкретным муниципальным округам',
xaxis_title = 'Количество посадочных мест',
yaxis_title = 'Улица',
xaxis = dict(range = [-10, 400]),
showlegend = False,
margin = dict(l = 0, r = 0, t = 100, b = 0))
fig.update_traces(hovertemplate = 'Количество: %{x}<br>Улица: %{y}')
fig.show()
Наибольшее среднее количество посадочных мест (медиана) среди топ-10 улиц с наибольшим числом объектов общественного питания с учетом принадлежности их частей конкретным муниципальным округам у улицы Арбат (48), Пятницкой улицы (46,5) и улицы Покровка (42), наименьшее - у Ходынского бульвара, улиц Ленинская Слобода и Сущевский вал (по 10), вероятнее всего это связано со значительным количеством заведений в крупных торговых центрах с небольшим количеством посадочных мест. Наибольший разброс количества посадочных мест характерен для улиц Новый Арбат и Арбат, а также Пятницкой и Кировоградской улиц.
Рекомендуется открыть заведение:
При развитии сети обратить внимание на возможность размещения заведений в крупных торговых центрах вне вышеуказанного района.